package com.sunday.minio.core;

import com.sunday.minio.autoconfiguration.MinioProperties;
import com.sunday.minio.entity.BaseResult;
import com.sunday.minio.entity.BatchWriteResponse;
import com.sunday.minio.entity.SingleWriteResponse;
import com.sunday.minio.entity.Snowball;
import com.sunday.minio.utils.MinioUtils;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.SnowballObject;
import io.minio.messages.DeleteObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * MinIO template
 *
 * @author zsy
 * @since 2023/10/13
 */
@Slf4j
@Data
@AllArgsConstructor
public class MinioTemplate implements MinioOperations {

    private MinioProperties minioProperties;

    private MinioClient minioClient;

    public SingleWriteResponse upload(String path) {
        return upload(defaultBucket(), path);
    }

    /**
     * 根据路径上传文件
     * 文件名name以uuid的形式保存，通过解析path获取fileType，利用当前时间生成前缀路径
     *
     * @param bucket 桶名称
     * @param path   上传文件完整路径 D:\app\minIO\447680d47859c66d417a7bbaaa70b21c.jpeg (文件名需要包含后缀，以便获取文件类型)
     * @return {@link SingleWriteResponse}
     */
    public SingleWriteResponse upload(String bucket, String path) {
        ObjectWriteResponse objectWriteResponse = uploadObject(bucket, MinioUtils.objectName(Paths.get(path)), path);
        return SingleWriteResponse.of(objectWriteResponse);
    }

    public SingleWriteResponse put(String filename, byte[] bytes) {
        return put(defaultBucket(), filename, bytes);
    }

    /**
     * 根据文件名和字节数组上传文件
     * 文件名name以uuid的形式保存，通过解析filename获取fileType，利用当前时间生成前缀路径
     *
     * @param bucket   桶名称
     * @param filename 文件名称(需要唯一,否则会覆盖)   putObject.txt (文件名需要包含后缀，以便获取文件类型)
     * @param bytes    文件内容字节
     * @return {@link SingleWriteResponse}
     */
    public SingleWriteResponse put(String bucket, String filename, byte[] bytes) {
        Path path = Paths.get(filename);
        ObjectWriteResponse objectWriteResponse = putObject(bucket, MinioUtils.objectName(path), MinioUtils.contentType(path), bytes);
        return SingleWriteResponse.of(objectWriteResponse);
    }

    public SingleWriteResponse put(MultipartFile multipartFile) {
        return put(defaultBucket(), multipartFile);
    }

    /**
     * 根据 MultipartFile 上传文件
     *
     * @param bucket        桶名称
     * @param multipartFile 文件对象 {@link MultipartFile}
     * @return {@link SingleWriteResponse}
     */
    public SingleWriteResponse put(String bucket, MultipartFile multipartFile) {
        Path path = Paths.get(multipartFile.getOriginalFilename());
        ObjectWriteResponse objectWriteResponse = putObject(bucket, MinioUtils.objectName(path), MinioUtils.contentType(path), () -> multipartFile.getInputStream());
        return SingleWriteResponse.of(objectWriteResponse);
    }

    public BatchWriteResponse uploadSnowball(Collection<Snowball> snowballs) {
        return uploadSnowball(defaultBucket(), snowballs);
    }

    /**
     * 根据 Collection Snowball 完成批量上传
     * <p>
     * ByteArrayInputStream类的close()方法没有实现任何操作，是因为ByteArrayInputStream不需要进行显式的资源释放或关闭。
     * ByteArrayInputStream是一个在内存中操作字节数组的输入流，它基于一个字节数组提供了读取数据的功能。由于它不依赖于外部资源（例如文件或网络连接），因此在使用完毕后并不需要显式地关闭或释放资源。
     * 在Java I/O中，通常会对那些涉及底层资源或与外部系统交互的流（如文件流、网络流等）进行关闭操作以释放资源和确保数据的完整性。但对于ByteArrayInputStream这样的内存流，关闭操作并不是必需的。
     * 如果你使用了其他类型的输入流，特别是那些打开了底层资源的流，务必在使用完毕后调用其close()方法来释放资源。
     *
     * @param bucket    桶名称
     * @param snowballs 批量文件上传对象 {@link Snowball} 的集合
     * @return {@link BatchWriteResponse}
     */
    public BatchWriteResponse uploadSnowball(String bucket, Collection<Snowball> snowballs) {
        List<BaseResult> baseResults = new ArrayList<>();
        ObjectWriteResponse objectWriteResponse = uploadSnowballObjects(bucket, () -> snowballs.stream().map(snowball -> {
            if (snowball.getFilename() != null) {
                String objectName = MinioUtils.objectName(snowball.getFilename());
                baseResults.add(BaseResult.of(bucket, objectName));
                return new SnowballObject(
                        objectName,
                        new ByteArrayInputStream(snowball.getBytes()),
                        snowball.getBytes().length,
                        null);
            } else {
                String objectName = MinioUtils.objectName(snowball.getPath());
                baseResults.add(BaseResult.of(bucket, objectName));
                return new SnowballObject(
                        objectName,
                        snowball.getPath());

            }
        }).toList());
        return BatchWriteResponse.of(objectWriteResponse, baseResults);
    }

    public BatchWriteResponse uploadMultipartFileSnowball(Collection<MultipartFile> multipartFiles) {
        return uploadMultipartFileSnowball(defaultBucket(), multipartFiles);
    }

    /**
     * 根据 Collection MultipartFile 完成批量上传
     *
     * @param bucket         桶名称
     * @param multipartFiles 批量文件上传对象 {@link MultipartFile} 的集合
     * @return {@link BatchWriteResponse}
     */
    public BatchWriteResponse uploadMultipartFileSnowball(String bucket, Collection<MultipartFile> multipartFiles) {
        List<Snowball> snowballs = invoke(() -> {
            List<Snowball> arrays = new ArrayList<>();
            for (MultipartFile multipartFile : multipartFiles) {
                try (InputStream inputStream = multipartFile.getInputStream()) {
                    arrays.add(Snowball.of(multipartFile.getOriginalFilename(), IOUtils.toByteArray(inputStream)));
                }
            }
            return arrays;
        });
        return uploadSnowball(bucket, snowballs);
    }

    /**
     * 复制文件
     *
     * @param copyTarget {@link MinioUtils#format(BaseResult)} 后的源文件完整路径 test@2024/01/26/448b136b537a38fb55565880436a15f9.txt
     * @param goalBucket 目标桶
     * @return {@link SingleWriteResponse}
     */
    public SingleWriteResponse copy(String copyTarget, String goalBucket) {
        BaseResult copy = MinioUtils.parse(copyTarget);
        ObjectWriteResponse objectWriteResponse = copyObject(
                copy.getBucket(),
                copy.getObject(),
                goalBucket,
                MinioUtils.objectName(Paths.get(copy.getObject())));
        return SingleWriteResponse.of(objectWriteResponse);
    }

    public void remove(String target) {
        BaseResult result = MinioUtils.parse(target);
        removeObject(result.getBucket(), result.getObject());
    }

    /**
     * 删除对象。
     *
     * @param targets {@link MinioUtils#format(BaseResult)} 后的源文件完整路径 test@2024/01/26/448b136b537a38fb55565880436a15f9.txt
     */
    public void remove(List<String> targets) {
        targets.stream()
                .map(MinioUtils::parse)
                .collect(
                        Collectors.groupingBy(
                                BaseResult::getBucket,
                                Collectors.mapping(result -> new DeleteObject(result.getObject()), Collectors.toSet())
                        )
                )
                .forEach((bucket, objects) -> removeObjects(bucket, objects));
    }

    /**
     * 获取对象的数据。
     * 返回必须在使用后关闭以释放网络资源。InputStream
     * GetObjectResponse extends FilterInputStream 一定要记得关闭流
     *
     * @param target {@link MinioUtils#format(BaseResult)} 后的源文件完整路径 test@2024/01/26/448b136b537a38fb55565880436a15f9.txt
     * @return {@link InputStream}
     */
    public InputStream get(String target) {
        BaseResult result = MinioUtils.parse(target);
        return getObject(result.getBucket(), result.getObject());
    }

    /**
     * 将对象的数据下载到文件。
     *
     * @param target {@link MinioUtils#format(BaseResult)} 后的源文件完整路径 test@2024/01/26/448b136b537a38fb55565880436a15f9.txt
     * @param path   下载文件路径 D:\app\minIO\测试3.txt
     */
    public void download(String target, String path) {
        BaseResult result = MinioUtils.parse(target);
        downloadObject(result.getBucket(), result.getObject(), path);
    }

}
